/*
 * rtc-test.c - rtc test driver
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/device.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/rtc.h>
#include <linux/time.h>
#include <linux/ktime.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include "common.h"

#include "../../rtc/lombo/rtc.h"

#undef MOD_NAME
#define MOD_NAME	"RTC_TEST"	/* for print message */
#undef RTC_NAME
#define RTC_NAME	"rtc0"		/* rtc device name */

/* test raw data, test abort time boundary */
static struct rtc_time test[] = {
	{59, 0, 0, 1, 1, 2000}, {60, 0, 0, 1, 1, 2000}, {0, 0, 0, 1, 1, 2000},
	{-1, 0, 0, 1, 1, 2000}, {1, 0, 0, 1, 1, 2000}, {0, 59, 0, 1, 1, 2000},
	{0, 60, 0, 1, 1, 2000}, {0, -1, 0, 1, 1, 2000}, {0, 1, 0, 1, 1, 2000},
	{0, 0, 23, 1, 1, 2000}, {0, 0, 24, 1, 1, 2000}, {0, 0, -1, 1, 1, 2000},
	{0, 0, 1, 1, 1, 2000}, {0, 0, 0, 0, 1, 2000}, {0, 0, 0, 31, 1, 2000},
	{0, 0, 0, 31, 1, 2000}, {0, 0, 0, 32, 1, 2000}, {0, 0, 0, 30, 4, 2000},
	{0, 0, 0, 31, 4, 2000}, {0, 0, 0, 30, 2, 2000}, {0, 0, 0, 29, 2, 2000},
	{0, 0, 0, 28, 2, 1999}, {0, 0, 0, 29, 2, 1999}, {0, 0, 0, 1, 12, 2000},
	{0, 0, 0, 1, 13, 2000}, {0, 0, 0, 1, 1, 2000}, {0, 0, 0, 1, 0, 2000},
	{0, 0, 0, 1, 2, 2000}, {0, 0, 0, 1, 1, 2100}, {0, 0, 0, 1, 1, 2101},
	{0, 0, 0, 1, 1, 1970}, {0, 0, 0, 1, 1, 1969}
};

/* test data, test abort week boundary */
static struct rtc_time week_test[] = {
	{0, 0, 0, 1, 1, 2000, 6}, {0, 0, 0, 1, 1, 2000, 5},
	{0, 0, 0, 1, 1, 2000, 7}, {0, 0, 0, 5, 1, 2000, 3},
	{0, 0, 0, 5, 1, 2000, 4}, {0, 0, 0, 5, 1, 2000, 5},
	{0, 0, 0, 2, 1, 2000, 0}, {0, 0, 0, 2, 1, 2000, -1},
};

/* test register, observe the change in value */
static struct rtc_time register_test = {55, 59, 23, 31, 11, 99};

/* default rtc time */
static struct rtc_time tm = {
	.tm_sec = 1,
	.tm_min = 1,
	.tm_hour = 1,
	.tm_mday = 1,
	.tm_mon = 1,
	.tm_year = 100,
};

/* default rtc alarm */
struct rtc_wkalrm alrm = {
	.time.tm_sec = 1,
	.time.tm_min = 1,
	.time.tm_hour = 1,
	.time.tm_mday = 1,
	.time.tm_mon = 1,
	.time.tm_year = 101,
	.enabled = 1,
};

/* rtc print time */
void print_time(struct rtc_time *tm)
{
	PRT_INFO("time is %d/%d/%d %d %02d:%02d:%02d\n", tm->tm_year + 1900,
		 tm->tm_mon + 1, tm->tm_mday, tm->tm_wday, tm->tm_hour,
		 tm->tm_min, tm->tm_sec);
}

/* rtc print alarm */
void print_alarm(struct rtc_wkalrm *alrm)
{
	PRT_INFO("alarm is %d/%d/%d %02d:%02d:%02d\n",
		 alrm->time.tm_year + 1900, alrm->time.tm_mon + 1,
		 alrm->time.tm_mday, alrm->time.tm_hour,
		 alrm->time.tm_min, alrm->time.tm_sec);
}

void time_transfer(struct rtc_time *tm)
{
	tm->tm_year -= 1900;
	tm->tm_mon -= 1;
}

/* rtc get time */
struct rtc_time rtc_get_time(struct rtc_device *rtc)
{
	struct rtc_time tm;
	int err = rtc_read_time(rtc, &tm);

	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc read time test failed\n\n");
		return tm;
	}

	PRT_INFO("rtc read time test succeeded\n");
	print_time(&tm);

	return tm;
}

/* rtc get alarm */
struct rtc_wkalrm rtc_get_alarm(struct rtc_device *rtc)
{
	struct rtc_wkalrm alarm;
	int err = rtc_read_alarm(rtc, &alarm);

	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc read alarm test failed\n\n");
		return alarm;
	}

	PRT_INFO("rtc read alarm test succeeded\n");
	print_alarm(&alarm);

	return alarm;
}

/* rtc read time test */
void rtc_read_time_test(void)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);

	PRT_INFO("rtc read time test start\n");
	rtc_get_time(rtc);
	PRT_INFO("end\n\n");
}

/* rtc set time test */
int rtc_set_time_test(struct rtc_time *rtc_tm)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	int err;
	struct rtc_time tm;

	PRT_INFO("rtc set time test start\n");

	tm = *rtc_tm;
	time_transfer(&tm);
	err = rtc_set_time(rtc, &tm);
	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc set time test failed\n");
		//print_time(&tm);
		PRT_INFO("end\n");
		return -1;
	}

	rtc_get_time(rtc);
	PRT_INFO("end\n");
	return 0;
}

/* rtc set week test */
void rtc_set_week_test(struct rtc_time *rtc_tm)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	int err;
	struct rtc_time tm;

	PRT_INFO("rtc set week test start\n");

	tm = *rtc_tm;
	time_transfer(&tm);
	err = rtc_set_time(rtc, &tm);
	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc set week test failed\n");
		print_time(&tm);
		PRT_INFO("end\n\n");
		return;
	}

	rtc_get_time(rtc);

	PRT_INFO("end\n\n");
}

/* rtc observe register test, pass few secs, observe register value */
void rtc_observe_register_test(struct rtc_time *rtc_tm)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	int err;
	struct rtc_time tm;

	PRT_INFO("rtc observe register test start\n");

	tm = *rtc_tm;
	err = rtc_set_time(rtc, &tm);
	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc observe register test failed\n\n");
		return;
	}

	rtc_get_time(rtc);
	PRT_INFO("after ten secs, observe register value");

	msleep(10000);
	rtc_get_time(rtc);

	PRT_INFO("end\n\n");
}

/* rtc initialize alarm test */
void rtc_initialize_alarm_test(void)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	struct rtc_wkalrm alarm;
	int err;

	PRT_INFO("rtc initialize alarm test start\n");

	err = rtc_initialize_alarm(rtc, &alarm);
	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc initialize alarm test failed\n\n");
		return;
	}

	PRT_INFO("end\n\n");
}

/* rtc read alarm test */
void rtc_read_alarm_test(void)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);

	PRT_INFO("rtc read alarm test start\n");
	rtc_get_alarm(rtc);
	PRT_INFO("end\n\n");
}

/* rtc set alarm test */
void rtc_set_alarm_test(struct rtc_wkalrm *alrm)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	int err;
	struct rtc_wkalrm alarm;

	PRT_INFO("rtc set alarm test start\n");

	alarm = *alrm;
	err = rtc_set_alarm(rtc, &alarm);
	if (IS_ERR(ERR_PTR(err))) {
		PRT_INFO("rtc set alarm test failed\n\n");
		return;
	}

	rtc_get_alarm(rtc);

	PRT_INFO("end\n\n");
}

/* rtc alarm callback test */
void rtc_alarm_callback_test(void)
{
	int n = 5;
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	struct rtc_wkalrm alarm = alrm;
	unsigned char flag;

	PRT_INFO("rtc alarm callback test start\n");

	rtc_get_time(rtc);
	rtc_alarm_irq_enable(rtc, 1);
	rtc_set_time(rtc, &tm);

	while (n--) {
		flag = 0;
		alarm.time = rtc_get_time(rtc);
		alarm.time.tm_sec += 5;
		rtc_set_alarm(rtc, &alarm);
		rtc_alarm_irq_enable(rtc, 1);
		alarm = rtc_get_alarm(rtc);
		PRT_INFO("after set alarm, alarm irq is %s\n",
			(alarm.enabled) ? "yes" : "no");
		if (alarm.enabled == 1)
			flag |= 1;

		msleep(5000);
		rtc_read_alarm(rtc, &alarm);
		PRT_INFO("alarm irq is %s\n", (alarm.enabled) ? "yes" : "no");
		if (alarm.enabled == 0)
			flag |= (1 << 1);
		if (flag == 3)
			PRT_INFO("alarm rang!\n");
		PRT_INFO("\n\n");
	}

	PRT_INFO("end\n\n");
}

/* rtc alarm stop test */
void rtc_alarm_stop_test(void)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);

	PRT_INFO("rtc alarm stop test start\n");

	rtc_timer_cancel(rtc, &(rtc->aie_timer));
	rtc_set_time(rtc, &tm);
	rtc_get_time(rtc);

	PRT_INFO("end\n\n");
}

/* rtc alarm start test */
void rtc_alarm_start_test(void)
{
	int ret;
	ktime_t min, now;
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);

	PRT_INFO("rtc alarm start test start\n");

	rtc_set_time(rtc, &tm);
	rtc_get_time(rtc);
	min = ktime_set(0, 0);
	now = rtc_tm_to_ktime(tm);
	now = ktime_add(now, min);

	ret = rtc_timer_start(rtc, &(rtc->aie_timer), now, ktime_set(0, 0));
	if (ret != 0) {
		PRT_INFO("rtc alarm start test failed\n\n");
		return;
	}

	PRT_INFO("end\n\n");
}

void rtc_clk_src_test(void)
{
#ifndef FPGA
	int rtc_clk_src = 0;
	struct rtc_device *rtc_dev = rtc_class_open(RTC_NAME);
	struct lombo_rtc *lombo_rtc = dev_get_drvdata(rtc_dev->dev.parent);
	reg_rtc_t *base = lombo_rtc->base;

#ifndef CONFIG_ARCH_LOMBO_N7V1
	/* manual switch */
	csp_rtc_set_clk_src(&(base->rtc_clk_ctrl), CLK_SRC0);
	csp_rtc_clk_src0_enable(&(base->rtc_clk_ctrl), ENABLE);
#endif

	rtc_clk_src = csp_rtc_get_clk_src_stat(&(base->rtc_clk_ctrl));
	if (rtc_clk_src == CLK_SRC_RCOSC) {
		csp_rtc_set_clk_src0(&(base->rtc_clk_ctrl), CLK_SRC_LFEOSC);
		mdelay(10);
		rtc_clk_src = csp_rtc_get_clk_src_stat(&(base->rtc_clk_ctrl));
		if (rtc_clk_src != CLK_SRC_LFEOSC)
			PRT_ERR("RCOSC switch to LFEOSC failed\n");
	} else {
		csp_rtc_set_clk_src0(&(base->rtc_clk_ctrl), CLK_SRC_RCOSC);
		mdelay(10);
		rtc_clk_src = csp_rtc_get_clk_src_stat(&(base->rtc_clk_ctrl));
		if (rtc_clk_src != CLK_SRC_RCOSC)
			PRT_ERR("LFEOSC switch to RCOSC failed\n");
		csp_rtc_set_clk_src0(&(base->rtc_clk_ctrl), CLK_SRC_LFEOSC);
		mdelay(10);
		rtc_clk_src = csp_rtc_get_clk_src_stat(&(base->rtc_clk_ctrl));
		if (rtc_clk_src != CLK_SRC_LFEOSC)
			PRT_ERR("RCOSC switch to LFEOSC failed\n");
	}
#ifndef CONFIG_ARCH_LOMBO_N7V1
	/* back to auto switch */
	csp_rtc_set_clk_src(&(base->rtc_clk_ctrl), CLK_SRC1);
#endif
#endif
}

/* rtc power off wakeup test */
void rtc_power_off_wakeup_test(void)
{
	struct rtc_device *rtc = rtc_class_open(RTC_NAME);
	struct rtc_time tm;
	struct rtc_wkalrm alrm;

	PRT_INFO("rtc power off wakeup test start\n");

	tm = rtc_get_time(rtc);
	alrm.time = tm;
	alrm.enabled = 1;
	alrm.time.tm_sec += 10;
	rtc_set_alarm(rtc, &alrm);
	print_alarm(&alrm);

	kernel_power_off();
	do_exit(0);

	PRT_INFO("end\n\n");
}

/* rtc test project, about time and alarm, operations are read and setting */
void rtc_test(void)
{
	int n = ARRAY_SIZE(test);
	int m = ARRAY_SIZE(week_test);
	int ret;

	PRT_INFO("start rtc_test\n");

	rtc_read_time_test();
	PRT_INFO("test cases are %d\n cases", n);
	while (n--) {
		PRT_INFO("test case num is %d\n", n);
		ret = rtc_set_time_test(&test[n]);
		if (ret == 0)
			PRT_INFO("test case succeed\n\n");
		else {
			PRT_INFO("test case fail, case is\n");
			time_transfer(&test[n]);
			print_time(&test[n]);
			PRT_INFO("\n\n");
		}
	}
	while (m--)
		rtc_set_week_test(&week_test[m]);
	rtc_observe_register_test(&register_test);

	rtc_set_alarm_test(&alrm);
	rtc_read_alarm_test();
	rtc_alarm_callback_test();
	rtc_alarm_stop_test();
	rtc_alarm_start_test();
	rtc_clk_src_test();
#ifdef CONFIG_PM
	rtc_power_off_wakeup_test();
#endif
	PRT_INFO("end rtc_test\n");
}
